﻿#define CIL
#define ADJUST_SHORT_LABELS
//#define DUMP_ASSEMBLY

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;

using alib.Enumerable;
using alib.Character;
using alib.Debugging;
using alib.Array;
using System.Windows.Markup;
using agree.configuration;

#if CIL
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using alib.Reflection;
using alib.Reflection.emit;
using alib.Memory;
using SysType = System.Type;
#endif

namespace agree
{
	[DebuggerDisplay("{ToString(),nq}")]
	public class qpi
	{
		public int i_feat = -1;
		public int ix_slot = -1;
		public qpi[] rg_qnxt;

#if DEBUG && false
		public override string ToString()
		{
			var tm = Grammars.DebugTypeMgr;
			String sn = "next:[ " + (rg_qnxt == null ? "" : rg_qnxt.Select(q => fcm.feat_arr[q.i_feat].feature.ToUpper()).StringJoin(" ")) + " ]";
			if (i_feat == -1)
				return "root. " + sn;
			return String.Format("{0} slot:{1} {2}",
				fcm.feat_arr[i_feat].feature.ToUpper(),
				ix_slot,
				sn);
		}
#endif
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///  required property init:
	///		Isometrics
	///		TypeUtils
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[ContentProperty("Paths")]
	public unsafe sealed class QuickCheck : has_grammar_base, ISupportInitializeNotification
	{
		public QuickCheck(IRtParent parent)
			: base(parent)
		{
		}

		//BootstrapFsPathList _pth;
		//public BootstrapFsPathList Paths
		//{
		//	get { return this._pth; }
		//	set { this._pth = value; }
		//}
		IEnumerable<String> rgp;
		public Object Paths
		{
			get { return rgp; }
			set
			{
				rgp = value as IEnumerable<String>;
				if (rgp == null)
				{
					throw new Exception();
				}
			}
		}

		public void EndInit()
		{
			if (im == null)
				throw new Exception();
			if (tu == null)
				throw new Exception();

			this.ifeat_args = im.RuleArgsPath.IsEmpty ? -1 : im.RuleArgsPath[0];

			this.Compile();
		}


		public int ifeat_args;


#if QC_CIL
		//public QuickCheck parse_qcs;
#else
		public TypeMgrCompiledFsPath[] gen_qcs;
		public TypeMgrCompiledFsPath[] parse_qcs;
#endif

		public bool _QuickCheck(Tfs tfs1, Tfs tfs2, int m2)
		{
			bool f_can_skip;
			var _rg1 = Tfs.QcPaths(this, tfs1, tfs1._top_edge.Mark);
			fixed (Edge.Flag* rg1 = _rg1, rg2 = Tfs.QcPaths(this, tfs2, m2))
				f_can_skip = _qc(_rg1.Length, rg1, rg2);
			//u_stats(ip => ((Stats._unification*)ip)->QuickCheck.Record(f_can_skip));
			return f_can_skip;
		}

		bool _qc(int c, Edge.Flag* pf1, Edge.Flag* pf2)
		{
			for (int i = 0; i < c; i++)
			{
				Edge.Flag lo, hi;
				if ((lo = *pf1) != (hi = *pf2))
				{
					if ((hi & lo) >= Edge.Flag.EtmLeafType)
						return true;
					else if (lo < hi)
					{
						if (lo != 0 && tu.UnifyTypesHiLo(lo, hi) < 0)
							return true;
					}
					else if (hi != 0 && tu.UnifyTypesHiLo(hi, lo) < 0)
						return true;
				}

				pf1++;
				pf2++;
			}
			return false;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void analyze_path(qpi qroot, Dictionary<int[], qpi> qcmap, int[] rgfix, int ix)
		{
			Debug.Assert(!qcmap.ContainsKey(rgfix));
			qpi q_resume;

			int j;
			for (j = rgfix.Length - 1; j > 0; j--)
				if (qcmap.TryGetValue(rgfix.Take(j).ToArray(), out q_resume))
				{
					rgfix = rgfix.Skip(j).ToArray();
					goto ok;
				}
			q_resume = qroot;
		ok:
			for (j = 0; j < rgfix.Length; j++)
			{
				int fix = rgfix[j];
				qpi qnxt;
				if (q_resume.rg_qnxt == null)
					q_resume.rg_qnxt = new qpi[1];
				else if ((qnxt = Array.Find(q_resume.rg_qnxt, y => y.i_feat == fix)) != null)
					goto already_have;
				else
				{
					var tmp = new qpi[q_resume.rg_qnxt.Length + 1];
					q_resume.rg_qnxt.CopyTo(tmp, 0);
					q_resume.rg_qnxt = tmp;
				}
				q_resume.rg_qnxt[q_resume.rg_qnxt.Length - 1] = qnxt = new qpi() { i_feat = fix };
			already_have:
				q_resume = qnxt;
			}
			Debug.Assert(q_resume.ix_slot == -1);
			q_resume.ix_slot = ix;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		int[] get_qc_feats(String s_path)
		{
			return s_path
				.Trim(Charset.outer_trim)
				.Split(Charset.dot_sp, StringSplitOptions.RemoveEmptyEntries)
				.Select((sf, z) =>
				{
					int i_feat = ftm.GetFeatureIndex(sf);
					if (z == 0 && i_feat != ifeat_args)
						throw new GrammarAuxiliaryFileException("Quick-check entry should start with '{0}'", im.RuleArgsPath);
					return i_feat;
				})
				.Skip(1)
				.ToArray();
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void Compile()
		{
#if QC_CIL
			//var rgp = AgreeConfig.Parser.Chart.Unification.QuickCheckPaths;
			//if (rgp == null)
			//	return;
#else
			IList<String> rap = tm.config.grammar.rule_args_path;
			var qcp1 = config.parser.chart.unification.s_quick_check_paths;
			var qcp2 = config.generator.chart.unification.s_quick_check_paths;
			if (qcp1 != null)
			{
				parse_qcs = qcp1.Select(s =>
				{
					IEnumerable<String> path = s.Trim(Charset.outer_trim).ToLower().Split(Charset.dot_sp, StringSplitOptions.RemoveEmptyEntries);
					if (path.Take(rap.Count).SequenceEqual(rap))
						path = path.Skip(rap.Count).ToArray();
					return new FsPath(path).Compile(tm);
				}).ToArray();
			}
			if (qcp2 != null)
			{
				if (qcp1 != null && (qcp1 == qcp2 || qcp1.SequenceEqual(qcp2, StringComparer.InvariantCultureIgnoreCase)))
					gen_qcs = parse_qcs;
				else
				{
					gen_qcs = qcp2.Select(s =>
					{
						IEnumerable<String> path = s.Trim(Charset.outer_trim).ToLower().Split(Charset.dot_sp, StringSplitOptions.RemoveEmptyEntries);
						if (path.Take(rap.Count).SequenceEqual(rap))
							path = path.Skip(rap.Count).ToArray();
						return new FsPath(path).Compile(tm);
					}).ToArray();
				}
			}
#endif

#if CIL
			int c;
			qpi qroot;
#endif
			c = rgp._Count();
			qroot = new qpi();
			Dictionary<int[], qpi> qcmap = new Dictionary<int[], qpi>(c, IntArrayEqualityComparer.Default);

			/// consolidate all the disparate quick-check paths according to minimal traversal
			foreach (var a in rgp.Select((p, ix) => new { ix, rgfix = get_qc_feats(p) }).OrderBy(_a => _a.rgfix.Length))
			{
				analyze_path(qroot, qcmap, a.rgfix, a.ix);
			}

			/// the traversal root, 'qroot' is sufficent for von-neumann use. If proceeding in the CIL case,
			/// convert the traversal into an unrolled customized fetching sequence
#if CIL
			DynamicMethod dm = new DynamicMethod(
										String.Empty,
										typeof(Edge.Flag[]),
										new[] { typeof(Tfs), typeof(int) }, // arg.1 'mark', arg.0 'tfs'
										typeof(QuickCheck),
										true)
										{ InitLocals = false };

			ILGenerator il = dm.GetILGenerator();

			_emit_qc_cil(il, qroot, c);

#if ADJUST_SHORT_LABELS
			int o_max_stack = (int)typeof(ILGenerator).GetField("m_maxStackSize", bf).GetValue(il);
			SignatureHelper o_sh = (SignatureHelper)typeof(ILGenerator).GetField("m_localSignature", bf).GetValue(il);
			Object o_scope = typeof(ILGenerator).Module.GetTypes().First(t => t.Name == "DynamicILGenerator").GetField("m_scope", bf).GetValue(il);
			List<Object> o_rg = (List<Object>)o_scope.GetType().GetField("m_tokens", bf).GetValue(o_scope);

			byte[] patch = il.AdjustShortLabels(null, 0);

			typeof(DynamicMethod).GetField("m_ilGenerator", bf).SetValue(dm, null);

			DynamicILInfo dil = dm.GetDynamicILInfo();
			{
				var dil_scope = typeof(DynamicILInfo).GetField("m_scope", bf).GetValue(dil);
				int dil_sig_tok = (int)typeof(DynamicILInfo).GetField("m_methodSignature", bf).GetValue(dil);
				byte[] dil_sig = (byte[])((List<Object>)dil_scope.GetType().GetField("m_tokens", bf).GetValue(dil_scope))[dil_sig_tok & 0xFFFFFF];

				int oix = o_rg.FindIndex(so =>
				{
					byte[] st = so as byte[];
					return st != null && st.Length == dil_sig.Length && ByteArrayEqualityComparer.Default.Equals(st, dil_sig);
				});
				if (oix == -1)
					throw new Exception();
				oix |= 0x11000000;
				typeof(DynamicILInfo).GetField("m_methodSignature", bf).SetValue(dil, oix);
				typeof(DynamicILInfo).GetField("m_scope", bf).SetValue(dil, o_scope);
			}

			dil.SetCode(patch, o_max_stack);
			dil.SetLocalSignature(o_sh.GetSignature());
#endif
			QcWalk = (Func<Tfs, int, Edge.Flag[]>)dm.CreateDelegate(typeof(Func<Tfs, int, Edge.Flag[]>));

#if DUMP_ASSEMBLY
			reflection._dump_dynamic_emit(tb =>
				{
					var mthb = tb.DefineMethod("pdq", MethodAttributes.Public | MethodAttributes.Static,
											   typeof(Edge.Flag[]),
											   new SysType[]
											{
												typeof(Tfs),		// arg.0 'tfs'
												typeof(int)			// arg.1 'mark'
											});
					mthb.InitLocals = false;
					ILGenerator _il = mthb.GetILGenerator();
					_emit_qc_cil(_il, qroot, c);
					return _il;
				});
#endif
			Console.WriteLine("QC paths CIL: {0} bytes", il.ILOffset);
#endif
		}

#if CIL
		const BindingFlags bf = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void _emit_qc_cil(ILGenerator il, qpi qroot, int c)
		{
			il.DeclareLocal(typeof(arr_tfs_entry).MakeByRefType(), true);	// loc.0	'(a)tfs.entries (managed ptr)'
			il.DeclareLocal(typeof(Edge.Flag).MakeByRefType(), true);		// loc.1	fetched results (managed ptr)
			il.DeclareLocal(typeof(Edge.Flag));								// loc.2	'f'
			il.DeclareLocal(typeof(int));									// loc.3	'nm'
			il.DeclareLocal(typeof(ushort).MakeByRefType(), true);			// loc.4	'phf'

			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldfld, typeof(Tfs).GetField("entries", bf));
			il.Emit(OpCodes.Ldc_I4_0);
			il.Emit(OpCodes.Ldelema, typeof(arr_tfs_entry));
			il.Emit(OpCodes.Stloc_0);

			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldflda, typeof(ArrayTfs).GetField("h", bf));
			il.Emit(OpCodes.Ldflda, typeof(ArrayTfs.hash_tab).GetField("pfirst", bf));
			il.Emit(OpCodes.Stloc_S, 4);

			il.EmitLdcI4(c);
			il.Emit(OpCodes.Newarr, typeof(Edge.Flag));
			il.Emit(OpCodes.Dup);											// return value from the emitted function
			il.Emit(OpCodes.Ldc_I4_0);
			il.Emit(OpCodes.Ldelema, typeof(Edge.Flag));
			il.Emit(OpCodes.Stloc_1);

			foreach (var qn in qroot.rg_qnxt)
			{
				il.Emit(OpCodes.Ldarg_1);
				il.Emit(OpCodes.Stloc_3);
				_emit_qc_walk_cil(il, qn);
			}

			il.Emit(OpCodes.Ldnull);
			il.Emit(OpCodes.Stloc_0);
			il.Emit(OpCodes.Ldnull);
			il.Emit(OpCodes.Stloc_1);

			il.Emit(OpCodes.Ret);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		static void _emit_qc_walk_cil(ILGenerator il, qpi qn)
		{
			/// rg[qn.ix_slot] = f = ArrayTfs.TryGetFlagsMark(phf, pe, i_feat, m, out nm);
			if (qn.ix_slot != -1)
			{
				il.Emit(OpCodes.Ldloc_1);
				il.EmitLdcI4(qn.ix_slot * sizeof(Edge.Flag));
				il.Emit(OpCodes.Add);
			}

			il.Emit(OpCodes.Ldloc_S, 4);		// phf
			il.Emit(OpCodes.Ldloc_0);			// pe
			il.EmitLdcI4(qn.i_feat);			// i_feat
			il.Emit(OpCodes.Ldloc_3);			// m
			il.Emit(OpCodes.Ldloca_S, (byte)3);	// out nm
			il.Emit(OpCodes.Call, typeof(ArrayTfs).GetMethod("TryGetFlagsMark", BindingFlags.Public | BindingFlags.Static));

			if (qn.ix_slot != -1)
			{
				if (qn.rg_qnxt != null)
				{
					il.Emit(OpCodes.Dup);
					il.Emit(OpCodes.Stloc_2);
				}

				il.Emit(OpCodes.Ldc_I4, (int)Edge.Flag.IdResolve);
				il.Emit(OpCodes.And);
				il.Emit(OpCodes.Stind_I4);

				if (qn.rg_qnxt != null)
					il.Emit(OpCodes.Ldloc_2);
			}

			if (qn.rg_qnxt != null)
			{
				Debug.Assert(qn.rg_qnxt.Length > 0);
				Label l = il.DefineLabel();

				il.Emit(OpCodes.Ldc_I4, (int)Edge.Flag.EtmNonBareType);
				il.Emit(OpCodes.And);
				il.Emit(OpCodes.Brfalse, l);

				for (int i = 0; i < qn.rg_qnxt.Length; i++)
				{
					if (i < qn.rg_qnxt.Length - 1)
						il.Emit(OpCodes.Ldloc_3);
					_emit_qc_walk_cil(il, qn.rg_qnxt[i]);
					if (i < qn.rg_qnxt.Length - 1)
						il.Emit(OpCodes.Stloc_3);
				}

				il.MarkLabel(l);
			}
		}
#endif

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// QC path-caching CIL entry point delegate
		/// </summary>
		/// <param name="tfs">must be an ArrayTfs</param>
		/// <param name="mark">starting mark. Note that 'args' is deleted from QC paths</param>
		/// <returns>array of path-terminal values</returns>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if CIL
		public Func<Tfs, int, Edge.Flag[]> QcWalk;
#else
		public Edge.Flag[] QcWalk(Tfs tfs, int mark)
		{
			Edge.Flag[] rgef = new Edge.Flag[c];
			foreach (var qn in qroot.rg_qnxt)
				_qc_walk(qn, rgef, tfs, mark);
			return rgef;
		}

		readonly qpi qroot;
		readonly int c;			/// number of types to fetch/cache, one per quick-check path terminus.

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void _qc_walk(qpi q, Edge.Flag[] rgef, Tfs tfs, int m)
		{
			int nm;
			Edge.Flag f = tfs.TryGetFlagsMark(q.i_feat, m, out nm);
			if (q.ix_slot != -1)
				rgef[q.ix_slot] = f & Edge.Flag.IdResolve;

			if (q.rg_qnxt != null && (f & Edge.Flag.EtmNonBareType) != 0)
				foreach (var qn in q.rg_qnxt)
					_qc_walk(qn, rgef, tfs, nm);
		}
#endif
	};
}
